// // Copyright (c) 2009 All Right Reserved // // vl // // 2009-01-01 // Contains ... using System; using System.Diagnostics.Contracts; using System.Globalization; using System.Text; using System.Xml.Linq; using System.Xml.Serialization; using LargoCommon.Abstract; using LargoCommon.Interfaces; namespace LargoCommon.Music { /// Melodic tone. /// Tone is defined as pitch placed in given bit Range with a given loudness. /// (it would possibly incorporate melodic ornaments?). /// It is planned to keep some properties of harmonic clusters /// (e.g. harmonic cover, potential or doubling). [Serializable] [XmlRoot] public sealed class MusicalTone : MusicalStrike, IComparable { #region Fields /// /// Musical pitch. /// private MusicalPitch pitch; /// /// Sound weight. /// private float weight; #endregion #region Constructors /// Initializes a new instance of the MusicalTone class. Serializable. public MusicalTone() { this.ToneType = MusicalToneType.Melodic; this.weight = -1; } /// Initializes a new instance of the MusicalTone class. /// Rhythmical range of bits. /// Number of musical bar. public MusicalTone(BitRange givenBitRange, int barNumber) : base(MusicalToneType.Melodic, givenBitRange, 0, barNumber) { this.weight = -1; } /// Initializes a new instance of the MusicalTone class. /// Musical pitch. /// Rhythmical range of bits. /// Musical loudness. /// Number of musical bar. public MusicalTone(MusicalPitch givenPitch, BitRange givenBitRange, MusicalLoudness loudness, int barNumber) : base(MusicalToneType.Melodic, givenBitRange, loudness, barNumber) { this.weight = -1; this.Pitch = givenPitch; //// if (this.IsEmpty) { return; } } /// /// Initializes a new instance of the MusicalTone class. /// /// Musical pitch. /// Rhythmic Order. /// Musical duration. /// Musical loudness. /// Number of musical bar. public MusicalTone(MusicalPitch givenPitch, byte givenRhythmicOrder, byte givenDuration, MusicalLoudness loudness, int barNumber) : base(MusicalToneType.Melodic, givenRhythmicOrder, givenDuration, loudness, barNumber) { this.weight = -1; this.Pitch = givenPitch; //// if (this.IsEmpty) { return; } } /// Initializes a new instance of the MusicalTone class. /// Type of tone. /// Rhythmical range of bits. /// Musical loudness. /// Number of musical bar. public MusicalTone(MusicalToneType toneType, BitRange givenBitRange, MusicalLoudness loudness, int barNumber) : base(toneType, givenBitRange, loudness, barNumber) { this.weight = -1; } /// Initializes a new instance of the MusicalTone class. /// Type of tone. /// Rhythmical order. /// Musical duration. /// Musical loudness. /// Number of musical bar. public MusicalTone(MusicalToneType toneType, byte givenRhythmicOrder, byte givenDuration, MusicalLoudness loudness, int barNumber) : base(toneType, givenRhythmicOrder, givenDuration, loudness, barNumber) { this.weight = -1; } /// /// Initializes a new instance of the class. /// /// The xml element. /// The rhythmic order. public MusicalTone(XElement xelement, byte rorder) { //// if (xelement == null) { return; } this.BarNumber = XmlSupport.ReadIntegerAttribute(xelement.Attribute("Bar")); //// this.bitRange = new BitRange(); //// this.bitRange.SetXElement(element); this.BitFrom = XmlSupport.ReadByteAttribute(xelement.Attribute("Start")); this.Duration = XmlSupport.ReadByteAttribute(xelement.Attribute("Length")); this.BitRange = new BitRange(rorder, this.BitFrom, (byte)this.Duration); //// string s = LibSupport.ReadStringAttribute(xelement.Attribute("ToneType")); //// this.ToneType = string.IsNullOrEmpty(s) ? MusicalToneType.Empty : (MusicalToneType)Enum.Parse(typeof(MusicalToneType), s); this.ToneType = MusicalToneType.Melodic; this.Loudness = DataEnums.ReadAttributeMusicalLoudness(xelement.Attribute("Loudness")); //// 2018/10 !?!?!? commented - instrument directed by status //// 2018/12 back to tone instruments var attrInstr = xelement.Attribute("Instrument"); if (attrInstr != null) { this.InstrumentNumber = XmlSupport.ReadByteAttribute(attrInstr); //// see lastInstrument ... } else { this.InstrumentNumber = (int)MidiMelodicInstrument.None; } var harmonicSystem = HarmonicSystem.GetHarmonicSystem(DefaultValue.HarmonicOrder); //// if (harmonicSystem == null) { return; } var xpitch = xelement.Attribute("Pitch"); if (xpitch == null) { return; } this.pitch = new MusicalPitch(harmonicSystem); this.pitch.SetXAttribute(xpitch); } #endregion #region Properties - Xml /// Gets Xml representation. /// Property description. public override XElement GetXElement { get { XElement xe; if (this.pitch != null) { xe = new XElement( "Tone", //// xmlMelTone.Add(new XAttribute("ToneType", this.ToneType)); new XAttribute("Bar", this.BarNumber), new XAttribute("Start", this.BitFrom), //// this.BitRange.GetXElement new XAttribute("Length", this.Duration), new XAttribute("Note", this.Pitch.ToString()), //// this.Pitch.GetXElement this.pitch.GetXAttribute); //// 2019/01 if (this.Loudness != MusicalLoudness.MeanLoudness) { xe.Add(new XAttribute("Loudness", this.Loudness.ToString())); //// } } else { xe = new XElement("Pause", new XAttribute("Length", this.Duration)); } return xe; } } #endregion #region Tone Properties /// /// Gets or sets the pause - pause following the note (e.g. because of note shortening) /// Duration of the pause is calculated separately. /// /// /// The pause. /// public MusicalPause Pause { get; set; } /// Gets or sets index to harmonic modality. /// Property description. public short ModalityIndex { get; set; } /// Gets or sets index to harmonic structure. /// Property description. public byte HarmonicIndex { get; set; } /// Gets or sets index for better identification of intervals (see HarmonicStateReal). /// Property description. public byte ToneIndex { get; set; } /// Gets musical pitch. /// Property description. public MusicalPitch Pitch { get { //// Contract.Requires(this.pitch != null); Contract.Ensures(Contract.Result() != null); //// Time optimization - critical time of comparison !? //// if (this.pitch == null) { throw new InvalidOperationException("Pitch is null."); } return this.pitch; } private set => this.pitch = value?.Clone() as MusicalPitch; } /// Gets weight of the tone. /// Property description. public float Weight { get { if (this.weight >= 0) { return this.weight; } var altitude = this.Pitch.OctaveAltitude(0f); // -4.0f var w = altitude >= DefaultValue.AfterZero && altitude <= DefaultValue.LargeNumber ? (short)this.Loudness / altitude : 0; this.weight = w; return this.weight; } } /// Gets a value indicating whether Is empty tone. /// Property description. /// Returns value. public override bool IsEmpty => this.pitch == null; /// Gets a value indicating whether is melodic tone. /// Property description. /// Returns value. public bool IsTrueTone => (this.ToneType == MusicalToneType.Melodic) && (this.pitch != null) && (this.Loudness > 0); /// /// Gets the melodic identifier. /// /// /// The melodic identifier. /// public override string MelodicIdentifier => $"{this.Pitch}#{this.BitFrom}/{this.Duration}"; /// /// Gets the rhythmic identifier. /// /// /// The rhythmic identifier. /// public override string RhythmicIdentifier => $"{this.BitFrom}/{this.Duration}"; #endregion #region Note Properties /// Gets or sets The MIDI note to modify (0x0 to 0x7F). /// General musical property. public override byte NoteNumber { get { if (this.Pitch == null) { return 0; } return this.Pitch.MidiKeyNumber; //// (this.Pitch != null) ? this.Pitch.MidiKeyNumber : (byte)0; } } /// Gets or sets The MIDI note to modify (0x0 to 0x7F). /// General musical property. public override string Note => MusicalProperties.GetNoteNameAndOctave(this.NoteNumber, DefaultValue.HarmonicOrder); /// /// Gets the note letter. /// /// Property description. public string NoteLetter => MusicalProperties.GetSingleNoteName(this.NoteNumber); /// /// Gets the note alter. /// /// Property description. public short NoteAlter => MusicalProperties.GetAlterSign(this.NoteNumber); #endregion #region Static Factory Methods /// /// Creates MusicalTone. /// /// Harmonic system. /// The bit range. /// Midi note. /// Bar number. /// Tone loudness. /// /// Returns value. /// public static IMusicalTone CreateMelodicTone( HarmonicSystem harmonicSystem, BitRange bitRange, byte midiNote, int barNumber, MusicalLoudness toneLoudness) { Contract.Requires(harmonicSystem != null); //// if (harmonicSystem == null) { return; } IMusicalTone musTone; if (midiNote > 0 && toneLoudness != MusicalLoudness.None) { //// musPitch = new MusicalPitch(harmonicSystem, midiNote); var musPitch = harmonicSystem.GetPitch(midiNote); var loudness = toneLoudness; //// loudness = 5; musTone = new MusicalTone(musPitch, bitRange, loudness, barNumber); } else { musTone = new MusicalPause(bitRange.Order, bitRange.Length, barNumber); } return musTone; } #endregion #region Static operators //// TICS rule 7@526: Reference types should not override the equality operator (==) //// public static bool operator ==(MusicalTone tone1, MusicalTone tone2) { return object.Equals(tone1, tone2); } //// public static bool operator !=(MusicalTone tone1, MusicalTone tone2) { return !object.Equals(tone1, tone2); } //// but TICS rule 7@530: Class implements interface 'IComparable' but does not implement '==' and '!='. /// /// Implements the operator <. /// /// The object1. /// The object2. /// /// Returns value. /// public static bool operator <(MusicalTone object1, MusicalTone object2) { if (object1 != null && object2 != null) { return object1.Pitch < object2.Pitch; } return false; } /// /// Implements the operator >. /// /// The object1. /// The object2. /// /// Returns value. /// public static bool operator >(MusicalTone object1, MusicalTone object2) { if (object1 != null && object2 != null) { return object1.Pitch > object2.Pitch; } return false; } /// /// Implements the operator <=. /// /// The object1. /// The object2. /// /// Returns value. /// public static bool operator <=(MusicalTone object1, MusicalTone object2) { if (object1 != null && object2 != null) { return object1.Pitch <= object2.Pitch; } return false; } /// /// Implements the operator >=. /// /// The object1. /// The object2. /// /// Returns value. /// public static bool operator >=(MusicalTone object1, MusicalTone object2) { if (object1 != null && object2 != null) { return object1.Pitch >= object2.Pitch; } return false; } #endregion #region Comparison /// Compare tones. /// Object to be compared. /// Returns value. public override int CompareTo(object value) { var mt = value as MusicalTone; if (mt?.pitch == null || this.pitch == null) { return 0; } return this.pitch.CompareTo(mt.Pitch); //// This kills the DataGrid //// throw new ArgumentException("Object is not a MusicalTone"); } /// Test of equality. /// Object to be compared. /// Returns value. public override bool Equals(object obj) { //// check null (this pointer is never null in C# methods) if (object.ReferenceEquals(obj, null)) { return false; } if (object.ReferenceEquals(this, obj)) { return true; } if (this.GetType() != obj.GetType()) { return false; } return this.CompareTo(obj) == 0; } /// Support of comparison. /// Returns value. public override int GetHashCode() { return this.Pitch == null ? 0 : this.Pitch.GetHashCode(); } #endregion #region Public methods /// /// Makes a deep copy of the MusicalStrike object. /// /// /// Returns object. /// public override object Clone() { return this.CloneTone(); } /// Makes a deep copy of the MusicalTone object. /// Returns object. public override object CloneTone() { var mt = new MusicalTone(this.Pitch, this.BitRange, this.Loudness, this.BarNumber) { //// (this.BarNumber) ModalityIndex = this.ModalityIndex, HarmonicIndex = this.HarmonicIndex, OrdinalIndex = this.OrdinalIndex, IsFromPreviousBar = this.IsFromPreviousBar, //// 2013/03 IsGoingToNextBar = this.IsGoingToNextBar, InstrumentNumber = this.InstrumentNumber, //// Channel = this.Channel, Staff = this.Staff, Voice = this.Voice }; return mt; } /// Accepts properties of the given tone. /// Melodic tone. public void SetMelTone(MusicalTone givenTone) { Contract.Requires(givenTone != null); //// if (givenTone == null) { return false; } this.SetMusicalTone(givenTone); this.Pitch = givenTone.Pitch; this.ModalityIndex = givenTone.ModalityIndex; this.HarmonicIndex = givenTone.HarmonicIndex; this.OrdinalIndex = givenTone.OrdinalIndex; this.IsFromPreviousBar = givenTone.IsFromPreviousBar; //// 2013/03 this.IsGoingToNextBar = givenTone.IsGoingToNextBar; this.InstrumentNumber = givenTone.InstrumentNumber; //// this.Channel = givenTone.Channel; this.Staff = givenTone.Staff; this.Voice = givenTone.Voice; } /// /// Sets the pitch. /// /// The given pitch. public void SetPitch(MusicalPitch givenPitch) { this.Pitch = givenPitch; } #endregion #region String representation /// String representation of the object. /// Returns value. public override string ToShortString() { var s = new StringBuilder(); if (!this.IsEmpty && this.Loudness > 0) { s.AppendFormat(CultureInfo.CurrentCulture, "{0,3}", this.Pitch); } else { s.Append(MusicalStrike.CPause); } return s.ToString(); } /// String representation of the object. /// Returns value. public override string ToString() { var s = new StringBuilder(); switch (this.ToneType) { case MusicalToneType.Rhythmic: s.AppendFormat(CultureInfo.CurrentCulture, "{0,3}", this.Loudness > 0 ? MusicalStrike.CBeat : MusicalStrike.CPause); break; case MusicalToneType.Melodic: s.AppendFormat(CultureInfo.CurrentCulture, "{0,3}", !this.IsEmpty && this.Loudness > 0 ? this.Pitch.ToString() : MusicalStrike.CPause); break; case MusicalToneType.Empty: break; } //// for (byte level = 2; level <= this.Duration; level++) { s.AppendFormat(CultureInfo.CurrentCulture, "{0,3}", MusicalStrike.CRepeat); } //// s.Append("(" + Duration.ToString(System.Globalization.CultureInfo.CurrentCulture.NumberFormat)); //// s.Append("<" + Loudness.ToString(System.Globalization.CultureInfo.CurrentCulture.NumberFormat) + ">"); s.AppendFormat(" <{0,3}>", this.Duration); //// s.Append(base.ToString()); return s.ToString(); } #endregion } }